class_name Ball
extends AnimatableBody2D

enum State {FREEFORM, CARRIED, SHOOT}

const BOUNCINESS := 0.8
const DISTANCE_HEIGHT_PASS := 130
const TUMBLE_HEIGHT_VELOCITY := 3.0
const DURATION_PASS_LOCK := 500
const DURATION_TUMBLE_LOCK := 200
const KICKOFF_PASS_DISTANCE := 30

@export var friction_air: float
@export var friction_ground: float

@onready var player_detection_area: Area2D = %PlayerDetectionArea
@onready var animation_player: AnimationPlayer = %AnimationPlayer
@onready var ball_sprite: Sprite2D = %BallSprite
@onready var scoring_ray_cast: RayCast2D = %ScoringRayCast
@onready var shoot_particles: GPUParticles2D = %ShootParticles
@onready var player_proximity_area: Area2D = %PlayerProximityArea

var carried: Player = null
var current_state: BallState
var state_factory := BallStateFactory.new()
var velocity := Vector2.ZERO
var height := 0.0
var height_velocity := 0.0
var spawn_position := Vector2.ZERO

func _ready() -> void:
	switch_state(State.FREEFORM)
	spawn_position = position
	GameEvent.team_reset_request.connect(on_team_reset.bind())
	GameEvent.kickoff_started_request.connect(on_kickoff_started.bind())
	
func _process(_delta: float) -> void:
	ball_sprite.position = Vector2.UP * height
	scoring_ray_cast.rotation = velocity.angle() 

func switch_state(state: State, state_data: BallStateData = BallStateData.new()) -> void:
	if current_state != null:
		current_state.queue_free()
	current_state = state_factory.get_fresh_state(state)
	current_state.setup(self, state_data, player_detection_area, carried, animation_player, ball_sprite, shoot_particles)
	current_state.state_register(switch_state.bind())
	current_state.name = "BallStateMachine"
	call_deferred("add_child", current_state)
	
func shoot(shoot_velocity: Vector2) -> void:
	velocity = shoot_velocity
	carried = null
	switch_state(State.SHOOT)
	
func pass_to(destination: Vector2, lock_duration: int = DURATION_PASS_LOCK) -> void:
	var direction := position.direction_to(destination)
	var distance := position.distance_to(destination)
	var intensity := sqrt(2 * distance * friction_ground)
	velocity = intensity * direction
	if distance > DISTANCE_HEIGHT_PASS:
		height_velocity = BallState.GRAVITY * distance / (1.8 * intensity)
	carried = null
	switch_state(State.FREEFORM, BallStateData.build().set_lock_duration(lock_duration))
	
func tumble(tumble_velocity: Vector2) -> void:
	velocity = tumble_velocity
	height_velocity = TUMBLE_HEIGHT_VELOCITY
	switch_state(State.FREEFORM, BallStateData.build().set_lock_duration(DURATION_TUMBLE_LOCK))
	
func stop() -> void:
	velocity = Vector2.ZERO
	
func can_air_interact() -> bool:
	return current_state != null and current_state.can_air_interact()
	
func can_air_connect(air_connect_min_height: float, air_connect_max_height: float) -> bool:
	return height >= air_connect_min_height and height < air_connect_max_height
	
func is_headed_for_scoring_area(scoring_area: Area2D) -> bool:
	if not scoring_ray_cast.is_colliding():
		return false
	return scoring_ray_cast.get_collider() == scoring_area
	
func on_team_reset() -> void:
	position = spawn_position
	
func on_kickoff_started() -> void:
	pass_to(spawn_position + Vector2.DOWN * KICKOFF_PASS_DISTANCE, 0)
	
func get_proximity_teamate_count(country: String) -> int:
	var players := player_proximity_area.get_overlapping_bodies()
	return players.filter(func(p: Player): return p.country == country).size()
